﻿using System.Reflection;
using NHibernate.Event;
using NHibernate.Type;

namespace ContactCenter.AuditEventListener
{
    public class AuditPostUpdateEventListener : AuditListenerBase, IPostUpdateEventListener
    {
        public void OnPostUpdate(PostUpdateEvent evt)
        {
            var dirtyProperties = evt.Persister.FindDirty(evt.State, evt.OldState, evt.Entity, evt.Session);

            foreach (var propIdx in dirtyProperties)
            {
                if (evt.Persister.PropertyTypes[propIdx] is ComponentType)
                {
                    ProcessComponentProperty(evt.Entity, evt.OldState[propIdx], evt.State[propIdx], evt.Session);
                }
                else
                {
                    var oldValue = GetValueFromState(evt.OldState, propIdx);
                    var newValue = GetValueFromState(evt.State, propIdx);
                    ProcessStandardProperty(evt.Entity, evt.Persister.PropertyNames[propIdx], oldValue, newValue, evt.Session);
                }
            }
        }

        /// <summary>
        /// Gets a property value from entity state array.
        /// </summary>
        private static string GetValueFromState(object[] state, int position)
        {
            var returnValue = state[position];
            return returnValue == null ? string.Empty : returnValue.ToString();
        }

        private static void ProcessStandardProperty(object entity, string propertyName, object oldValue, object newValue, IEventSource sourceSession)
        {
            if (!newValue.Equals(oldValue))
            {
                PersistAuditInfo(entity, propertyName, oldValue, newValue, AuditActions.Modified, sourceSession);
            }
        }

        /// <summary>
        /// Detects changes on a component property.
        /// </summary>
        /// <remarks>
        /// Components are detected as a single property changed on the entity, so if we process them as standard properties,
        /// we'd simply get a ToString() of the object.
        /// In this case, we've to loop on all component's properties and compare them one by one.
        /// </remarks>
        private static void ProcessComponentProperty(object entity, object oldState, object newState, IEventSource sourceSession)
        {
            PropertyInfo[] oldComponentValues = oldState.GetType().GetProperties();
            PropertyInfo[] newComponentValues = newState.GetType().GetProperties();

            for (int propIdx = 0; propIdx < oldComponentValues.Length; propIdx++)
            {
                if (oldComponentValues[propIdx].CanRead && newComponentValues[propIdx].CanRead)
                {
                    var oldValue = oldComponentValues[propIdx].GetValue(oldState, null);
                    var newValue = newComponentValues[propIdx].GetValue(newState, null);
                    if (!newValue.Equals(oldValue))
                    {
                        PersistAuditInfo(entity, oldComponentValues[propIdx].Name, oldValue, newValue, AuditActions.Modified, sourceSession);
                    }
                }
            }
        }
    }
}
